package thread.semaphore;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

/**
 * 试用下信号器使用
 *
 * @author wei.yang on 2018/8/7
 */
class Resource {

    /**
     * 信号枪，用于约束线程并发数，
     */
    private Semaphore semaphore = new Semaphore(MAX_THREAD, true);

    private static ConcurrentHashMap<Model, Boolean> sources = new ConcurrentHashMap<>();

    /**
     * 线程限流，阀值大小：即一次性只允许最多n个线程一起运行
     */
    private static final int MAX_THREAD = 3;

    private static final int MAX = 10;

    static {
        for (int i = 0; i < MAX; i++) {
            Model model = new Model(i);
            sources.put(model, false);
        }
    }

    /**
     * 拿一个资源
     * semaphore.acquire()可以定一个参数，用于说明一次性消耗的信号量，造成每次能够运行N/n个线程一起执行
     *
     * @return 资源，有可能拿不到
     * @throws InterruptedException 中断异常
     */
    Model useResource() throws InterruptedException {
        /*
         * 强要，可以指定每次要几个屏障数
         */
        semaphore.acquire();
        return findNextAvailableModel();
    }

    /**
     * 做人不能强要，先问问。
     * 问题是这个方法用的时候也不阻塞，当无可用的信号量时线程就什么也不做了，直接相当于任务丢弃
     *
     * @return 获取资源
     */
    Model useByCheck() {
        if (semaphore.tryAcquire()) {
            return findNextAvailableModel();
        }
        return null;
    }

    /**
     * 标记一个model已经可以用了
     *
     * @param model model
     */
    void release(Model model) {
        if (markFinish(model)) {
            // 可以指定每次释放几个屏障数
            semaphore.release();
        }
    }

    /**
     * 找一个可用的资源，没有的话就返回null
     *
     * @return 资源信息
     */
    private Model findNextAvailableModel() {
        Iterable<Model> models = sources.keySet();
        for (Model m : models) {
            if (!sources.get(m)) {
                sources.put(m, true);
                return m;
            }

        }
        return null;
    }

    /**
     * 标记一下这个资源已经用完了，可以复用了
     *
     * @param model 资源
     * @return 是否重置成功
     */
    private boolean markFinish(Model model) {
        if (sources.containsKey(model)) {
            Boolean result = sources.put(model, false);
            return result == null ? false : result;
        }
        return false;
    }
}
